\section{进程}
\subsection{进程号}
getpid()：获取当前进程号，getppid()：获取父进程的进程号。
\begin{longlisting}
\cppfile{code/thread/ex4/main.cpp}
\caption{}
\label{}
\end{longlisting}

fork():函数调用成功时返回进程ID失败则返回-1。
\begin{longlisting}
\cppfile{code/thread/ex5/main.cpp}
\caption{}
\label{}
\end{longlisting}

调用系统命令：
\begin{longlisting}
\cppfile{code/thread/ex6/main.cpp}
\caption{}
\label{}
\end{longlisting}

exec()在当前系统的可执行路径中根据指定的文件名找到合适的可执行文件名，并用它替代调用进程的内容，和fork不一样在于，exec()族函数执行成功后不会返回，\textcolor{red}{这是因为执行的新程序已经占用了当前的进程空间和资源，这些资源包括代码段，数据段，堆栈等，特门都已经被新的内容取代，而进程的ID乘标志性信息仍然是原来的东西，即exec()函数族在原有的进程的壳上运行自己的程序，只有程序调用失败，系统才返回-1。}
\begin{longlisting}
\cppfile{code/thread/ex7/main.cpp}
\caption{}
\label{}
\end{longlisting}

\mint{cpp}{int pipe(int filedes[2]);}
数组中的filedes是一个文件描述符的数组，用于保存管道返回值的两个文件描述符。数组中第一个元素为了读操作而创建和打开的，而第二个元素是为了写操作而创建打开的。
\begin{longlisting}
\cppfile{code/thread/ex8/main.cpp}
\caption{}
\label{}
\end{longlisting}

当管道的写端没有关闭时，如果请求的字节数大于阈值PIPE\_BUF，写操作的返回值是管道中目前的数据字节数。如果请求的字节数目不大于PIPE\_BUF，则返回管道中现有数据字节数(此时，管道中数据量小于请求的数据量)，或者返回请求的字节数（此时管道中数据量表小于请求的数据量）
\begin{remark}
    PIPE\_BUF在include/Linux/limits.h中定义，不同的内核版本可能会有所不同，Posix.1要求PIPE\_BUF至少为512字节。
\end{remark}
管道进行写入操作的时候，当写入数据的数目小于128K的时候是非原子的，如果把父进程中的两次写入字节都该位128K，可以发现写入管道的数据量大于128K字节时。缓冲区的数据将被连续地写入管道，知道数据完全写完位置，如果没有进程读数据，则已知阻塞。
\textcolor{red}{代码缺失}
%\begin{longlisting}
%\cppfile{code/ex9/main.cpp}
%\caption{}
%\label{}
%\end{longlisting}

命名管道：命名管道的工作方式和普通管道非常相似，但是也有一些明显的区别。
\begin{itemize}
    \item 在文件系统中目录管道以特殊文件的形式存在。
    \item 不同进程可以通过命名管道共享数据。
\end{itemize}
对命名管道FIFO来说，IO操作和普通的管道IO操作基本上是一样的，二者之间存在一个主要的区别。在FIFO中，必须使用一个open()函数来显式的建立连接到管道的通道。一般来说FIFO总是处于阻塞状态。也就是说，如果命名管道FIFO打开时设置了读权，则读进程一直“阻塞”，直到其它进程打开该FIFO并向管道中写入数据。这个阻塞动作反过来也成立，如果一个进程打开一个管道写数据，当没有进程从管道读数据的时候，写管道操作也是阻塞的，直到已经写入的数据被读出后，才进行写入操作。如果不希望在进行命名管道操作时发生阻塞，可以在open调用中使用O\_NONBLOCK标识，以关闭默认的阻塞动作。
\section{进程间的通信方式}
\subsection{消息队列}
消息队列是内核地址空间的内部链表，通过Linux内核在各个进程之间传递内容，消息的顺序的发送到消息队列中，并以几种不同的方式从队列中获取，每个消息队列可以用IPC标识符唯一地进行标识。内核中的消息队列是通过IPC的标识符来区别的，不同的消息队列之间是相对独立的。每个消息队列中的消息右构成一个独立的链表。
\begin{figure}[H]
    \centering
    \includegraphics[width=\textwidth]{kernel_msg.pdf}
    \caption{消息机制在内核中的实现}
\end{figure}
结构list\_head形成一个链表，而结构msg\_msg之中的m\_list成员是一个struct list\_head类型的变量，通过此变量，消息形成了一个链表，在查找和插入时，对m\_list域进行偏移操作就可以找到对应的消息体位置。内核操作的代码在头文件<linux/msg.h>和<linux/msg.c>中，主要实现的是插入消息和取出消息操作。
\subsubsection{ftok()函数}
ftok()函数将路径名称和项目的表示转变成一个系统V的IPC键值。其原型如下：
\begin{cpp}
    #include<sys/types.h>
    #include<sys/ipc.h>
    key_t ftok(const char *pathname,int proj_id);
\end{cpp}
其中pathname必须是已经存在的目录，而proj\_id则是一个8位的值，通常用a,b等表示。建立目录\mint{bash}{mkdir -p /ipc/msg/}
然后用代码生成一个键值：
\begin{cpp}
    key_t key;
    char *msgpath="/ipc/msg/"
    key = tfok(msgpath,'a');
    if(key!=-1)
    printf("成功建立KEY\n");
    else
    printf("建立KEY失败\n");
\end{cpp}
\subsubsection{获得消息msgget()函数}
创建一个消息队列，或者访问一个现有的队列，可以使用函数msgget()，函数原型如下：
\begin{cpp}
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/msg.h>
    int msgget(key_t key,int msgflg);
\end{cpp}
msgget()函数的参数说明：
\begin{enumerate}
    \item 第一个参数key：0（IPC\_PRIVATE）:会建立新的消息队列，大于1的32位整数：视参数msgflg来确定操作。通常要求来源于ftok返回的IPC键值。
    \item 第二个参数msgflg:0取表系队列标识符，若不存在则函数会报错。IPC\_CREAT:当msgflg\&IPC\_CREAT为真时，如果内核还总不存在键值和key相等的消息队列，则新建一个消息队列rugosa存在这样的消息队列，返回此消息队列的标识符。IPC\_CREAT|IPC\_EXCL:如果内核中不存在键值和key相等的消息队列，则新建一个消息队列rugosa存在这样的消息队列则报错。
\end{enumerate}
返回出错的原因：
\begin{itemize}
    \item ACCES：指定的消息队列已存在，但调用进程没有权限访问它
    \item     EEXIST：key指定的消息队列已存在，而msgflg中同时指定IPC\_CREAT和IPC\_EXCL标志
    \item     ENOENT：key指定的消息队列不存在同时msgflg中没有指定IPC\_CREAT标志
    \item     ENOMEM：需要建立消息队列，但内存不足
    \item     ENOSPC：需要建立消息队列，但已达到系统的限制
\end{itemize}


如果只使用了IPC\_CREATE，mesget()函数或者返回新创建消息队列的消息队列标识符，或者会返回现有的具有同一关键字的队列的标识符。如果同时使用了IPC\_EXCL和IPC\_CREAT，那么将可能会有两个结果：创建一个新的队列，如果该队列存在，则调用将出错，并返回-1。IPC\_EXCL本身是没有什么用处的，但是在IPC\_CREAT组合使用时，它可以用于保证没有一个现存的队列为了访问而被打开。创建一个消息队列：
\begin{cpp}
    key_t key;
    int msg_flags,msg_id;
    msg_flags = IPC_CREAT|IPC_EXCL;
    msg_id = msgget(key,msg_flags|0x0666)
    if(-1 == msg_id)
    {
            printf("消息建立失败\n");
            return 0;
        }
\end{cpp}
\subsubsection{发送消息msgsnd()函数}
一旦获得队列标识符，用户就可以在该消息队列上执行相关操作了。为了向队列传递消息，用户可以使用msgsnd()函数：
\begin{cpp}
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/msg.h>
    int msgsnd(int msgid,const void *msgp,size_t msgsz,int msgflg);
\end{cpp}
msgsnd()函数的参数如下：
\begin{itemize}
    \item 第一个参数msgid：队列标识符，它是前面调用msgget()获得的返回值。
    \item 第二个参数msgp：void类型的指针，指向一个消息缓冲区。
    \item 第三个参数msgsz：包含消息的大小，以字节为单位，其中不包括消息类型的长度（4个字节）。
\end{itemize}

错误代码如下：
\begin{itemize}
    \item EAGAIN：参数msgflg设为IPC\_NOWAIT，而消息队列已满
    \item EIDRM：标识符为msqid的消息队列已被删除
    \item EACCESS：无权限写入消息队列
    \item EFAULT：参数msgp指向无效的内存地址
    \item EINTR：队列已满而处于等待情况下被信号中断
    \item EINVAL：无效的参数msqid、msgsz或参数消息类型type小于0
\end{itemize}
msgsnd()为阻塞函数，当消息队列容量满或消息个数满会阻塞。消息队列已被删除，则返回EIDRM错误；被信号中断返回E\_INTR错误。
如果设置IPC\_NOWAIT消息队列满或个数满时会返回-1，并且置EAGAIN错误。
msgsnd()解除阻塞的条件有以下三个条件：
\begin{enumerate}
    \item 不满足消息队列满或个数满两个条件，即消息队列中有容纳该消息的空间。
    \item msqid代表的消息队列被删除。
    \item 调用msgsnd函数的进程被信号中断。
\end{enumerate}
msgflg参数可以设置为0（表示忽略），也可以设置为IPC\_NOWAIT，则调用进程将被终端（阻塞），知道可以写消息位置。下面的代码向已经打开的消息队列发送消息：
\begin{cpp}
    struct msgbuf{//消息的结构
            int mtype;
            char mtext[10];
        }
    int msg_sflags://消息的标记
    int msg_id;//消息ID识别号
    struct msgbuf msg_mbuf;//建立消息结构变量
    msg_sflags = IPC_NOWAIT;//直接读取消息，不等待
    msg_mbuf.mtype = 10;//消息大小为10字节
    memcpy(msg_mbuf.mtext,"测试消息",sizeof("测试消息"));//将数据赋值入消息数据缓冲区
    ret = msgsnd(msg_id,&nsg_mbuf,sizeof("测试消息"),msg_sflags);//向消息ID发送消息
    if(-1 == ret){
            printf("发送消息队列");
        }
\end{cpp}
首先将要发送的消息打包到msg\_mbuf.mtext域中，然后调用msgsnd发送消息给内核。这里mtype设置了类型为10,当接收时必须设置此域为10,才能接收到这时发送的消息。msgsnd()函数的msg\_id是之前msgget创建的。
\subsubsection{接收消息msgrcv()函数}
当获得队列标识符后，用户就可以开始在该消息队列上执行消息队列的接收操作。msgrev()函数用于接收队列标识符中的消息，函数原型如下：
\begin{cpp}
    #include<sys/types.h>
    #include<sys/ipc.h>
    #include<sys/msg.h>
    ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtype,int msgflg);
\end{cpp}
\begin{itemize}
    \item msgrcv()函数的第一个参数msqid是用来指定，在消息获取过程中所使用的队列（该值由前面调用msgget()得到的返回值）。
    \item 第2个参数msgp代表消息缓冲区变量的地址，获取的消息将存放在这里。
    \item 第3个参数msgsz代表消息缓冲区结构的大小,不含消息类型占用的4个字节。
    \item 第4个参数mtype指定要从队列中获取的信息类型。为0则内核将查找队列中具有匹配类型的第一个到达的消息，>0:结合搜类型等于msgtyp的第一个消息，<0：接受类型等于或者小于msgtype绝对值得第一个消息。
    \item msgflg
          \begin{itemize}
              \item 0:阻塞式接受消息，没有该类型的小时msgrcv函数已知阻塞等待。
              \item IPC\_NOWAIT：如果没有返回条件的消息调用立即返回，此时错误码为ENOMSG。
              \item IPC\_EXCEPT:和msgtype配合使用返回队列中第一个类型不是msgtype的消息。
              \item IPC\_NOERROR：如果队列中满足条件的消息内容大于所请求的size字节，则把该消息阶段，阶段部分将被丢弃。
          \end{itemize}

\end{itemize}
如果把IPC\_NOWAIT作为一个标识传送给该函数，而队列中没有任何消息，则该次调用将会向调用进程返回ENOMSG。否则，调用进程将被阻塞，直到msgrcv()参数的消息到达队列位置。如果在客户等待消息的时候队列被删除了，则返回EIDRM。如果在进程阻塞并等待消息的到来时捕获到一个信号，则返回EINTR。函数msgrcv的使用如下：
\begin{cpp}
    msg_rflags = IPC_NOWAIT|MSG_NOERROR;
    ret = msgrcv(msg_id,&msg_mbuf,10,10,msg_rflags);//接收消息
    if(-1 == ret)
    printf(-1 == ret)
    else
    printf("结合搜消息成功，长度:%d\n",ret);//打印消息
\end{cpp}
上面的代码将mtype设置为10,可以获得之前发送的内核的消息（因为之前发送的mtype值也设置为10），msgrcv返回值为接收到消息长度。
错误代码：
\begin{itemize}
    \item E2BIG：消息数据长度大于msgsz而msgflag没有设置IPC\_NOERROR
    \item      EIDRM：标识符为msqid的消息队列已被删除
    \item      EACCESS：无权限读取该消息队列
    \item      EFAULT：参数msgp指向无效的内存地址
    \item      ENOMSG：参数msgflg设为IPC\_NOWAIT，而消息队列中无消息可读
    \item      EINTR：等待读取队列内的消息情况下被信号中断
\end{itemize}
msgrcv()解除阻塞的条件有以下三个：
\begin{enumerate}
    \item 消息队列中有了满足条件的消息。
    \item msqid代表的消息队列被删除。
    \item 调用msgrcv()的进程被信号中断。
\end{enumerate}

\subsubsection{消息控制msgctl()函数}
函数原型：
\begin{cpp}
    #include<sts/types.h>
    #include<sys.ipc.h>
    #include<sys/msg.h>
    int msgctl(msgid,int cmd,struct msgid_ds *buf);
\end{cpp}
msgctl()向内核发送一个cmd命令，内核根据此判断进行何种操作，buf为应用层和内核空间进行数据交换的指针。几种的cmd可以为如下值：
\begin{itemize}
    \item IPC\_STAT:获取队列的msqid\_ds结构，并把它存放在buf变量所指定的地址中，通过这种方式，应用层可以获取当前消息队列的设置情况，例如是否有消息到来，消息队列的缓冲区设置等。
    \item IPC\_SET：设置队列的msqid\_ds结构的ipc\_pern成员值，它是从buf中取得该值的。通过IPC\_SET命令，应用乘可以设置消息队列的状态，例如修改消息队列的权限，是其他用户可以访问或者不能访问当前队列：甚至可以设置消息队列的某些当前值来伪装。
    \item IPC\_RMID：内核删除队列。使用此命令后，内核会把此消息队列从系统中删除。
\end{itemize}
出错：
\begin{itemize}
    \item EACCESS：参数cmd为IPC\_STAT，确无权限读取该消息队列
    \item EFAULT：参数buf指向无效的内存地址
    \item EIDRM：标识符为msqid的消息队列已被删除
    \item EINVAL：无效的参数cmd或msqid
    \item EPERM：参数cmd为IPC\_SET或IPC\_RMID，却无足够的权限执行
\end{itemize}
